package ai;

import java.util.PriorityQueue;

import game.Game;
import game.Position;
import game.PositionComparator;

public class MinMax2 extends MinmaxImproved
{

	// PriorityQueue to store the best three moves
	private PriorityQueue<Position> bestThree;
	private final static int MAX_RECURSION = 2;		//Laag 1
	private final static int MAX_DEPTH = 6;			//Laag 2
	
	//The amount of best moves to check
	private final static int MAX_BEST_MOVES_TO_CHECK = 3;

	public MinMax2(Game game, byte side)
	{
		super(game, side);
	}
	
	public int getZet()
	{
		this.copyPlayingField(game.getPlayingField());
		newQueue();
		return chooseMove(0).getColumn();
	}
	
	private void newQueue()
	{
		this.bestThree = new PriorityQueue<Position>(343, new PositionComparator());
	}
	
	
	public Position chooseMove(int depth)
	{
		//If the best move leads to a win we shouldnt look any further
		if (depth == MAX_RECURSION)
		{
			
			if (depth * MAX_DEPTH % 2 == 0)
			{
				//Call minmax
				minmax(new Position(this.aiPlayingField, true), this.side, 0, depth);
			}
			//It's human turn
			else
			{
				//Call minmax
				minmax(new Position(this.aiPlayingField, true), this.opponent, 0, depth);
			}
			
			//System.out.println("Schreeuw2");
			//DEBUG:
			//Position returnValue = this.bestThree.poll();
			//System.out.println("Schreeuw2 returning: "+returnValue);
			//return returnValue;
			
			//return the best move from the priority queue
			return this.bestThree.poll();
		}
		
		
		
		//Als er nog geen queue geplaatst is
		if(this.bestThree.isEmpty())
		{
			//System.out.println("Schreeuw1");
			
			//To save the returnvalue of chooseBestThree
			Position reply;
			
			//If it's the computers turn
			//Calculate the current depth which is:
			//MAX_DEPTH times the depth of this recursive loop
			
			
			if (depth * MAX_DEPTH % 2 == 0)
			{
				//Call minmax
				reply = minmax(new Position(this.aiPlayingField, true), this.side, 0, depth);
			}
			//It's human turn
			else
			{
				//Call minmax
				reply = minmax(new Position(this.aiPlayingField, true), this.opponent, 0, depth);
			}
			
			//If the best move leads to a win we shouldnt look any further
			if (reply.getStrength() == WIN_VALUE ||
					reply.getStrength() == LOSE_VALUE)
			{
				//System.out.println("Schreeuw2");
				//return the best move from the priority queue
				return this.bestThree.poll();
			}
			
			//DEBUG:
			//Position returnValue = chooseMove(depth + 1);
			//System.out.println("Schreeuw1 returning: "+returnValue);
			//return returnValue;
			
			//Recursieve aanroep met depth+1
			return chooseMove(depth + 1);
		}
		//Als er al wel object in de queue staan
		else
		{
			//System.out.println("Schreeuw3 aantal items in de queue:"+this.bestThree.size());
			
			//De best position (default to minimum value)
			int bestValue = MINIMUM_VALUE;
			byte side;
			
			//Lokaal de queue opslaan
			PriorityQueue<Position> priorityQueue = this.bestThree;
			
			//De globale priorityqueue weer opnieuw maken
			newQueue();
			
			//Lokale Position objecten
			Position pos, replyPos, bestReply = null;
			int bestmove = 0;
			
			//Check the best moves from the queue
			for (int i = 0; i < MAX_BEST_MOVES_TO_CHECK && ! priorityQueue.isEmpty(); i++)
			{
				pos = priorityQueue.poll();
				/* DEBUG:
				System.out.println("test - depth: "+depth+" i: "+i);
				System.out.flush();
				*/
				//If it's the computers turn
				//Calculate the current depth which is:
				//MAX_DEPTH times the depth of this recursive loop
				if (depth * MAX_DEPTH % 2 == 0)
				{
					side = this.side;
				}
				//It's human turn
				else
				{
					side = this.opponent;
				}
				
				//Call minmax
				minmax(pos, side, 0, depth);
				
				//Recursively call ourself
				replyPos = chooseMove(depth + 1);
				
				/* DEBUG:
				for (int t = 0; t < depth; t++)
				{
					System.out.print("	");
				}
				System.out.println("Strength: "+replyPos.getStrength()+" + het object: " + replyPos);
				*/
				
				//Select the best choice from the moves
				if (bestValue < replyPos.getStrength())
				{
					bestValue = replyPos.getStrength();
					bestmove = pos.getColumn();
					bestReply = replyPos;
				}
			}
			
			
			//DEBUG:
			//System.out.println("Schreeuw3 returning: "+new Position(bestmove, bestValue, bestReply.getDepth(), bestReply.getPlayingField()));
			
			return new Position(bestmove, bestValue, bestReply.getDepth(), bestReply.getPlayingField(), false);
		}
		
	}
	
	
	//Copy the playingfield to another array
	protected void copyPlayingField(byte[][] source, byte[][] target)
	{
		//For each column
		for (int x = 0; x < Game.XSIZE;x++)
		{
			//For each row
			for (int y = 0; y < Game.YSIZE; y++)
			{
				target[x][y] = source[x][y];
			}
		}
	}
	
	
	//Place a token in a column in a given playingField
	protected void placeToken(int column, byte side, byte[][] target)
	{
		//For each row
		for (int j = Game.YSIZE - 1; j >= 0; j--)
		{
			if (target[column][j] == Game.EMPTY)
			{
				target[column][j] = side;
				return;
			}
		}
	}
	

	//Remove a token from a column in a target playingfield
	protected void removeToken(int column, byte[][] target)
	{
		//For each row
		for (int j = 0; j < Game.YSIZE; j++)
		{
			if (target[column][j] != Game.EMPTY)
			{
				target[column][j] = Game.EMPTY;
				return;
			}
		}
	}
	

	//A function that checks if a column is full in the target playingfield
	public boolean columnIsFull(int column, byte[][]target)
	{
		return (target[column][0] != Game.EMPTY);
	}
	
	
	public Position minmax(Position pos, byte side, int depth, int outerDepth)
	{
		byte opponent;
		int value;
		int bestmove = 0;
		byte[][] playingField = pos.getPlayingField();
		
		//Increment the counter
		this.counter++;

		//Base case
		int basecase = evaluateField(this.side, playingField);
		if (depth >= MAX_DEPTH || basecase == LOSE_VALUE || basecase == WIN_VALUE)
		{
			//Return the strength of the current field,
			//the depth,
			//and the field itself
			return new Position(basecase, (outerDepth * MAX_RECURSION) + depth, playingField, true);
		}

		if (side == this.side)
		{
			opponent = this.opponent;
			value = MINIMUM_VALUE;
		}
		else
		{
			opponent = this.side;
			value = MAXIMUM_VALUE;
		}
		
		
		//Main loop
		Position reply;
		Position answer = pos;
		for (int i = 0; i < Game.XSIZE; i++)
		{
			if ( ! this.columnIsFull(i, playingField) )
			{
				placeToken(i, side, playingField);
				reply = minmax(new Position(playingField, false), opponent, depth+1, outerDepth);
				removeToken(i, playingField);


				//Update the return value if we've found a better posibility
				if (side == this.side && reply.getStrength() > value ||
						side == this.opponent && reply.getStrength() < value)
				{
					value = reply.getStrength();
					bestmove = i;
					answer = reply;
				}
				
				//Add the information from depth 0 to the queue
				if (depth == 0)
				{
					//Make a position object
					Position position = new Position(i, reply.getStrength(), reply.getDepth(), reply.getPlayingField(), true);
					
					//Add it to the priority queue
					bestThree.add(position);
				}
			}
		}
		
		//Return the:
		//beste kolom,
		//de strength waarde,
		//de depth
		//het playingfield
		return new Position(bestmove, answer.getStrength(), answer.getDepth(), answer.getPlayingField(), false);
	}
	
	
	//A adapted evaluateField function that only looks at a single position
	public int evaluateField(byte playerSide, byte[][] playingField, int column, int previousStrength)
	{
		final int TWO_IN_A_ROW = 4;
		final int THREE_IN_A_ROW = 32;
		
		
		int tokensInARowCounter;
		int spaceInARowCounter;
		int strength = 0;
		boolean countingOwnTokens;
		byte side;
		
		
		//Find last inserted token
		int x = column;
		int y = Game.YSIZE - 1;
		while (y >= 0 && playingField[x][y] == Game.EMPTY)
		{
			y--;
		}
		
		//Save the side of the token
		side = playingField[x][y];
		
		
		
		
		//#################### Horizontal ####################
		//Set the counter to 1 because we've already found one-on-row
		tokensInARowCounter = 1;
		spaceInARowCounter = 1;
		
		//Horizontal count (to the left)
		countingOwnTokens = true;
		for (int i = x-1; i >= 0; i--)
		{
			if (side == playingField[i][y])
			{
				//If the token is the same side as the side we are checking for
				//We increment our own token if we haven't encountered a empty token yet
				if (countingOwnTokens)
				{
					tokensInARowCounter++;
				}
				
				//Increment the space token to keep track if we can still make a four in a row
				spaceInARowCounter++;
			}
			else if (playingField[i][y] == Game.EMPTY)
			{
				//If we find a empty column we should stop counting our own tokens
				//becuase x_xxx is not the same as for on a row
				countingOwnTokens = false;
				
				//Increment the space token to keep track if we can still make a four in a row
				spaceInARowCounter++;
			}
			else
			{
				break;
			}
		}
		
		//Horizontal count (to the right)
		countingOwnTokens = true;
		for (int i = x+1; i < Game.XSIZE; i++)
		{
			if (side == playingField[i][y])
			{
				//If the token is the same side as the side we are checking for
				//We increment our own token if we haven't encountered a empty token yet
				if (countingOwnTokens)
				{
					tokensInARowCounter++;
				}
				
				//Increment the space token to keep track if we can still make a four in a row
				spaceInARowCounter++;
			}
			else if (playingField[i][y] == Game.EMPTY)
			{
				//If we find a empty column we should stop counting our own tokens
				//becuase x_xxx is not the same as for on a row
				countingOwnTokens = false;
				
				//Increment the space token to keep track if we can still make a four in a row
				spaceInARowCounter++;
			}
			else
			{
				break;
			}
		}
		
		//Check if we should count these tokens
		if (spaceInARowCounter >= 4)
		{
			//Add the tokens to the strength if it is still possible to create a four in a row with these tokens
			switch (tokensInARowCounter)
			{
			case 1:
				strength++;
				break;
			case 2:
				strength += TWO_IN_A_ROW;
				break;
			case 3:
				strength += THREE_IN_A_ROW;
				break;
			case 4:
				default:
				if (side == this.side)
				{
					return WIN_VALUE;
				}
				else
				{
					return LOSE_VALUE;
				}
			}
		}
		
		
		
		//#################### Vertical ####################
		//Set the counter to 1 because we've already found one-on-row
		tokensInARowCounter = 1;
		spaceInARowCounter = 1;
		
		//Vertical count (up)
		countingOwnTokens = true;
		for (int j = y+1; j < Game.YSIZE; j++)
		{
			if (side == playingField[x][j])
			{
				//If the token is the same side as the side we are checking for
				//We increment our own token if we haven't encountered a empty token yet
				if (countingOwnTokens)
				{
					tokensInARowCounter++;
				}
				
				//Increment the space token to keep track if we can still make a four in a row
				spaceInARowCounter++;
			}
			else if (playingField[x][j] == Game.EMPTY)
			{
				//If we find a empty column we should stop counting our own tokens
				//becuase x_xxx is not the same as for on a row
				countingOwnTokens = false;
				
				//Increment the space token to keep track if we can still make a four in a row
				spaceInARowCounter++;
			}
			else
			{
				break;
			}
		}
		
		//Vertical count (down)
		countingOwnTokens = true;
		for (int j = y-1; j >= 0; j--)
		{
			if (side == playingField[x][j])
			{
				//If the token is the same side as the side we are checking for
				//We increment our own token if we haven't encountered a empty token yet
				if (countingOwnTokens)
				{
					tokensInARowCounter++;
				}
				
				//Increment the space token to keep track if we can still make a four in a row
				spaceInARowCounter++;
			}
			else if (playingField[x][j] == Game.EMPTY)
			{
				//If we find a empty column we should stop counting our own tokens
				//becuase x_xxx is not the same as for on a row
				countingOwnTokens = false;
				
				//Increment the space token to keep track if we can still make a four in a row
				spaceInARowCounter++;
			}
			else
			{
				break;
			}
		}
		
		//Check if we should count these tokens
		if (spaceInARowCounter >= 4)
		{
			//Add the tokens to the strength if it is still possible to create a four in a row with these tokens
			switch (tokensInARowCounter)
			{
			case 1:
				strength++;
				break;
			case 2:
				strength += TWO_IN_A_ROW;
				break;
			case 3:
				strength += THREE_IN_A_ROW;
				break;
			case 4:
				default:
				if (side == this.side)
				{
					return WIN_VALUE;
				}
				else
				{
					return LOSE_VALUE;
				}
			}
		}
		
		
		
		
		
		
		//#################### Diaginal (lower left to upper right) ####################
		//Set the counter to 1 because we've already found one-on-row
		tokensInARowCounter = 1;
		spaceInARowCounter = 1;
		
		//Diaginal count (to lower left)
		countingOwnTokens = true;
		for (int i = x-1; i >= 0; i--)
		{
			for (int j = y+1; j < Game.YSIZE; j++)
			{
				if (side == playingField[i][j])
				{
					//If the token is the same side as the side we are checking for
					//We increment our own token if we haven't encountered a empty token yet
					if (countingOwnTokens)
					{
						tokensInARowCounter++;
					}
					
					//Increment the space token to keep track if we can still make a four in a row
					spaceInARowCounter++;
				}
				else if (playingField[i][j] == Game.EMPTY)
				{
					//If we find a empty column we should stop counting our own tokens
					//becuase x_xxx is not the same as for on a row
					countingOwnTokens = false;
					
					//Increment the space token to keep track if we can still make a four in a row
					spaceInARowCounter++;
				}
				else
				{
					break;
				}
			}
		}
		
		//Diaginal count (to upper right)
		countingOwnTokens = true;
		for (int i = x+1; i < Game.XSIZE; i++)
		{
			for (int j = y-1; j >= 0; j--)
			{
				if (side == playingField[i][j])
				{
					//If the token is the same side as the side we are checking for
					//We increment our own token if we haven't encountered a empty token yet
					if (countingOwnTokens)
					{
						tokensInARowCounter++;
					}
					
					//Increment the space token to keep track if we can still make a four in a row
					spaceInARowCounter++;
				}
				else if (playingField[i][j] == Game.EMPTY)
				{
					//If we find a empty column we should stop counting our own tokens
					//becuase x_xxx is not the same as for on a row
					countingOwnTokens = false;
					
					//Increment the space token to keep track if we can still make a four in a row
					spaceInARowCounter++;
				}
				else
				{
					break;
				}
			}
		}
		
		//Check if we should count these tokens
		if (spaceInARowCounter >= 4)
		{
			//Add the tokens to the strength if it is still possible to create a four in a row with these tokens
			switch (tokensInARowCounter)
			{
			case 1:
				strength++;
				break;
			case 2:
				strength += TWO_IN_A_ROW;
				break;
			case 3:
				strength += THREE_IN_A_ROW;
				break;
			case 4:
				default:
				if (side == this.side)
				{
					return WIN_VALUE;
				}
				else
				{
					return LOSE_VALUE;
				}
			}
		}
		
		
		
		
		
		//#################### Diaginal (upper left to lower right) ####################
		//Set the counter to 1 because we've already found one-on-row
		tokensInARowCounter = 1;
		spaceInARowCounter = 1;
		
		//Diaginal count (to upper left)
		countingOwnTokens = true;
		for (int i = x-1; i >= 0; i--)
		{
			for (int j = y-1; j >= 0; j--)
			{
				if (side == playingField[i][j])
				{
					//If the token is the same side as the side we are checking for
					//We increment our own token if we haven't encountered a empty token yet
					if (countingOwnTokens)
					{
						tokensInARowCounter++;
					}
					
					//Increment the space token to keep track if we can still make a four in a row
					spaceInARowCounter++;
				}
				else if (playingField[i][j] == Game.EMPTY)
				{
					//If we find a empty column we should stop counting our own tokens
					//becuase x_xxx is not the same as for on a row
					countingOwnTokens = false;
					
					//Increment the space token to keep track if we can still make a four in a row
					spaceInARowCounter++;
				}
				else
				{
					break;
				}
			}
		}
		
		//Diaginal count (to lower right)
		countingOwnTokens = true;
		for (int i = x+1; i < Game.XSIZE; i++)
		{
			for (int j = y+1; j < Game.YSIZE; j++)
			{
				if (side == playingField[i][j])
				{
					//If the token is the same side as the side we are checking for
					//We increment our own token if we haven't encountered a empty token yet
					if (countingOwnTokens)
					{
						tokensInARowCounter++;
					}
					
					//Increment the space token to keep track if we can still make a four in a row
					spaceInARowCounter++;
				}
				else if (playingField[i][j] == Game.EMPTY)
				{
					//If we find a empty column we should stop counting our own tokens
					//becuase x_xxx is not the same as for on a row
					countingOwnTokens = false;
					
					//Increment the space token to keep track if we can still make a four in a row
					spaceInARowCounter++;
				}
				else
				{
					break;
				}
			}
		}
		
		//Check if we should count these tokens
		if (spaceInARowCounter >= 4)
		{
			//Add the tokens to the strength if it is still possible to create a four in a row with these tokens
			switch (tokensInARowCounter)
			{
			case 1:
				strength++;
				break;
			case 2:
				strength += TWO_IN_A_ROW;
				break;
			case 3:
				strength += THREE_IN_A_ROW;
				break;
			case 4:
				default:
				if (side == this.side)
				{
					return WIN_VALUE;
				}
				else
				{
					return LOSE_VALUE;
				}
			}
		}
		
		
		
					
		//If the current side is the side of the AI then the value is positive instead else it's negative
		if (side == this.side)
		{
			previousStrength += strength;
		}
		else
		{
			previousStrength -= strength;
		}
		
		
		return previousStrength;
	}
	
	
	/* DEBUG FUNCTIE
	private void dumpArray(int[][] pField, int uitkomst)
	{
		
		System.out.println("dumpArray functie");
		for (int y = 0; y < Game.YSIZE; y++)
		{
			System.out.print("		");
			for (int x = 0; x < Game.XSIZE; x++)
			{
				System.out.print(pField[x][y]);
			}
			System.out.println();
		}
		System.out.println("Met als uitkomst: "+uitkomst);
		
	}
	*/
}
			